/*
* $RCSfile: TIFFImageReader.java,v $
*
*
* Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for
* use in the design, construction, operation or maintenance of any
* nuclear facility.
*
* $Revision: 1.13 $
* $Date: 2007/12/19 20:17:02 $
* $State: Exp $
*/
/*
* ImageI/O-Ext - OpenSource Java Image translation Library
* http://www.geo-solutions.it/
* http://java.net/projects/imageio-ext/
* (C) 2007 - 2016, GeoSolutions
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of GeoSolutions nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY GeoSolutions ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GeoSolutions BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package it.geosolutions.imageioimpl.plugins.tiff;
import it.geosolutions.imageio.maskband.DatasetLayout;
import it.geosolutions.imageio.plugins.tiff.BaselineTIFFTagSet;
import it.geosolutions.imageio.plugins.tiff.PrivateTIFFTagSet;
import it.geosolutions.imageio.plugins.tiff.TIFFColorConverter;
import it.geosolutions.imageio.plugins.tiff.TIFFDecompressor;
import it.geosolutions.imageio.plugins.tiff.TIFFField;
import it.geosolutions.imageio.plugins.tiff.TIFFImageReadParam;
import it.geosolutions.imageio.stream.input.FileImageInputStreamExtImpl;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
import com.sun.media.imageioimpl.common.ImageUtil;
import com.sun.media.imageioimpl.common.PackageUtil;
public class TIFFImageReader extends ImageReader {
/**
* This class can be used to cache basic information about a tiff page.
* <p>
* Notice that we hold {@link IIOMetadata} using {@link SoftReference}s since
* they might be big so we might want to reclaim the least used ones.
*
* @author Simone Giannecchini, GeoSoltions S.A.S.
*
*/
private final static class PageInfo {
private SoftReference<TIFFImageMetadata> imageMetadata;
protected PageInfo(
TIFFImageMetadata imageMetadata,
boolean bigtiff,
int[] bitsPerSample,
char[] colorMap,
int compression,
int height,
int numBands,
int photometricInterpretation,
int width,
int tileOrStripWidth,
int tileOrStripHeight,
int planarConfiguration,
boolean isImageTiled,
int samplesPerPixel,
int[] sampleFormat,
int[] extraSamples,
Double noData) {
this.imageMetadata = new SoftReference<TIFFImageMetadata>(imageMetadata);
this.bigtiff = bigtiff;
this.bitsPerSample = bitsPerSample;
this.colorMap = colorMap;
this.compression = compression;
this.height = height;
this.numBands = numBands;
this.photometricInterpretation = photometricInterpretation;
this.width = width;
this.tileOrStripWidth = tileOrStripWidth;
this.tileOrStripHeight = tileOrStripHeight;
this.planarConfiguration = planarConfiguration;
this.isImageTiled = isImageTiled;
this.samplesPerPixel = samplesPerPixel;
this.sampleFormat = sampleFormat;
this.extraSamples = extraSamples;
this.noData = noData;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (bigtiff ? 1231 : 1237);
result = prime * result + Arrays.hashCode(bitsPerSample);
result = prime * result + Arrays.hashCode(colorMap);
result = prime * result + compression;
result = prime * result + Arrays.hashCode(extraSamples);
result = prime * result + height;
result = prime * result + (isImageTiled ? 1231 : 1237);
result = prime * result + ((noData == null) ? 0 : noData.hashCode());
result = prime * result + numBands;
result = prime * result + photometricInterpretation;
result = prime * result + planarConfiguration;
result = prime * result + Arrays.hashCode(sampleFormat);
result = prime * result + samplesPerPixel;
result = prime * result + tileOrStripHeight;
result = prime * result + tileOrStripWidth;
result = prime * result + width;
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof PageInfo))
return false;
PageInfo other = (PageInfo) obj;
if (bigtiff != other.bigtiff)
return false;
if (!Arrays.equals(bitsPerSample, other.bitsPerSample))
return false;
if (!Arrays.equals(colorMap, other.colorMap))
return false;
if (compression != other.compression)
return false;
if (!Arrays.equals(extraSamples, other.extraSamples))
return false;
if (height != other.height)
return false;
if (isImageTiled != other.isImageTiled)
return false;
if (noData == null) {
if (other.noData != null)
return false;
} else if (!noData.equals(other.noData))
return false;
if (numBands != other.numBands)
return false;
if (photometricInterpretation != other.photometricInterpretation)
return false;
if (planarConfiguration != other.planarConfiguration)
return false;
if (!Arrays.equals(sampleFormat, other.sampleFormat))
return false;
if (samplesPerPixel != other.samplesPerPixel)
return false;
if (tileOrStripHeight != other.tileOrStripHeight)
return false;
if (tileOrStripWidth != other.tileOrStripWidth)
return false;
if (width != other.width)
return false;
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "PageInfo [bigtiff=" + bigtiff + ", bitsPerSample="
+ Arrays.toString(bitsPerSample) + ", colorMap=" + Arrays.toString(colorMap)
+ ", compression=" + compression + ", extraSamples="
+ Arrays.toString(extraSamples) + ", height=" + height + ", isImageTiled="
+ isImageTiled + ", numBands=" + numBands + ", photometricInterpretation="
+ photometricInterpretation + ", planarConfiguration=" + planarConfiguration
+ ", sampleFormat=" + Arrays.toString(sampleFormat) + ", samplesPerPixel="
+ samplesPerPixel + ", tileOrStripHeight=" + tileOrStripHeight
+ ", tileOrStripWidth=" + tileOrStripWidth + ", width=" + width
+ ", noData=" + noData + "]";
}
private boolean bigtiff = false;
private int[] bitsPerSample;
private char[] colorMap;
private int compression;
private int height = -1;
private int numBands = -1;
private int photometricInterpretation;
private int width = -1;
private int tileOrStripWidth = -1, tileOrStripHeight = -1;
private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
private boolean isImageTiled= false;
private int samplesPerPixel;
private int[] sampleFormat;
private int[] extraSamples;
private Double noData = null; //Using a Double to allow null value.
}
private static final boolean DEBUG = false; // XXX 'false' for release!!!
/** Constant Value for External Mask suffix*/
private static final String MASK_SUFFIX = ".msk";
/** Constant Value for External Overview suffix*/
private static final String OVR_SUFFIX = ".ovr";
private int magic = -1;
private Map<Integer, PageInfo> pagesInfo= new HashMap<Integer, PageInfo>();
private boolean bigtiff = false;
// The current ImageInputStream source.
ImageInputStream stream = null;
// True if the file header has been read.
boolean gotTiffHeader = false;
// true if we already have parsed metadata for this element
boolean initialized = false;
ImageReadParam imageReadParam = getDefaultReadParam();
// Stream metadata, or null.
TIFFStreamMetadata streamMetadata = null;
// The current image index.
int currIndex = -1;
// Metadata for image at 'currIndex', or null.
TIFFImageMetadata imageMetadata = null;
/**
* A <code>List</code> of <code>Long</code>s indicating the stream positions of the start of the
* IFD for each image. Entries are added as needed.
*/
List<Long> imageStartPosition = new ArrayList<Long>();
// The number of images in the stream, if known, otherwise -1.
int numImages = -1;
// The ImageTypeSpecifiers of the images in the stream.
// Contains a map of Integers to Lists.
HashMap<Integer, List<ImageTypeSpecifier>> imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>();
BufferedImage theImage = null;
int width = -1;
int height = -1;
int numBands = -1;
int tileOrStripWidth = -1, tileOrStripHeight = -1;
int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
int compression;
int photometricInterpretation;
int samplesPerPixel;
int[] sampleFormat;
int[] bitsPerSample;
int[] extraSamples;
char[] colorMap;
int sourceXOffset;
int sourceYOffset;
int srcXSubsampling;
int srcYSubsampling;
int dstWidth;
int dstHeight;
int dstMinX;
int dstMinY;
int dstXOffset;
int dstYOffset;
int tilesAcross;
int tilesDown;
int pixelsRead;
int pixelsToRead;
private boolean isImageTiled= false;
protected Double noData = null;
// BAND MASK RELATED FIELDS
/** {@link DatasetLayout} implementation containing info about overviews and masks*/
private TiffDatasetLayoutImpl layout;
/** External File containing TIFF masks*/
private File externalMask;
/** External File containing TIFF Overviews*/
private File externalOverviews;
/** External File containing TIFF masks overviews*/
private File maskOverviews;
public TIFFImageReader(ImageReaderSpi originatingProvider) {
super(originatingProvider);
}
public void setInput(Object input,
boolean seekForwardOnly,
boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata);
// Clear all local values based on the previous stream contents.
resetLocal();
if (input != null) {
if (!(input instanceof ImageInputStream)) {
throw new IllegalArgumentException
("input not an ImageInputStream!");
}
this.stream = (ImageInputStream)input;
// Check for external masks/overviews
if (input instanceof FileImageInputStreamExtImpl) {
FileImageInputStreamExtImpl stream = (FileImageInputStreamExtImpl) input;
// Getting File path
File inputFile = stream.getFile();
if (inputFile != null) {
// Getting Parent
File parent = inputFile.getParentFile();
// Getting Mask file name
File mask = new File(parent, inputFile.getName() + MASK_SUFFIX);
// Check if exists and can be read
if (mask.exists() && mask.canRead()) {
externalMask = mask;
// Getting external Mask Overviews
File mskOverviews = new File(mask.getAbsolutePath() + OVR_SUFFIX);
// Check if the file exists and can be read
if (mskOverviews.exists() && mskOverviews.canRead()) {
maskOverviews = mskOverviews;
}
}
// Getting Overviews file name
File ovr = new File(parent, inputFile.getName() + OVR_SUFFIX);
// Check if exists and can be read
if (ovr.exists() && ovr.canRead()) {
externalOverviews = ovr;
}
}
}
} else {
this.stream = null;
}
// Creating the New DatasetLayout for handling Overviews and Masking
layout = new TiffDatasetLayoutImpl();
}
// Do not seek to the beginning of the stream so as to allow users to
// point us at an IFD within some other file format
private void readHeader() throws IIOException {
if (gotTiffHeader) {
return;
}
if (stream == null) {
throw new IllegalStateException("Input not set!");
}
// Create an object to store the stream metadata
this.streamMetadata = new TIFFStreamMetadata();
try {
int byteOrder = stream.readUnsignedShort();
if (byteOrder == 0x4d4d) {
streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
} else if (byteOrder == 0x4949) {
streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
} else {
processWarningOccurred(
"Bad byte order in header, assuming little-endian");
streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
magic = stream.readUnsignedShort();
if (magic != 42 && magic != 43) {
processWarningOccurred(
"Bad magic number in header, continuing");
}
if (magic == 43)
bigtiff=true;
else
bigtiff=false;
// Seek to start of first IFD
long offset = -1;
switch(magic) {
case 42:
offset = stream.readUnsignedInt();
break;
case 43:
int alwaysEight = stream.readUnsignedShort();
if (alwaysEight != 8){
processWarningOccurred("No BigTiff file format");
}
int alwaysZero = stream.readUnsignedShort();
if (alwaysZero != 0){
processWarningOccurred("No BigTiff file format");
}
offset = stream.readLong();
break;
}
if (offset >= 0) {
imageStartPosition.add(Long.valueOf(offset));
stream.seek(offset);
} else
processWarningOccurred("Error calculating offset");
} catch (IOException e) {
throw new IIOException("I/O error reading header!", e);
}
gotTiffHeader = true;
}
/**
* Method used for populating reader's {@link DatasetLayout}
*
* @throws IIOException
*/
private void defineDatasetLayout() throws IIOException {
if (!(layout.getNumInternalOverviews() == -1 && layout.getNumInternalMasks() == -1)) {
return;
}
// Initialize
readHeader();
// Getting Image Number
int numImg;
try {
numImg = getNumImages(true);
} catch (IOException e) {
throw new IIOException(e.getMessage(), e);
}
// Extracting the TIFF Tag NewSubfileType from each Image
int numOverviews = 0;
int numMasks = 0;
int numMaskOverView = 0;
// If not all the Images Metadata have been loaded, loop through images in order to add them
// if(pagesInfo != null && (numImg != pagesInfo.size())){
// Getting current Index which will be restored at the end of the operation
int currentIdx = currIndex;
// Loop the images
for (int i = 0; i < numImg; i++) {
// Seek to the image index i
seekToImage(i);
// Getting PageInfo
PageInfo info = pagesInfo.get(i);
// Getting Metadata
TIFFImageMetadata metadata = info.imageMetadata.get();
// Getting TIFF TAG_NEW_SUBFILE_TYPE
TIFFField f = metadata.getTIFFField(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
// Checking if exists
if (f != null) {
Object data = f.getData();
// Checking if the data is LONG
if (data instanceof long[]) {
long ldata = ((long[]) data)[0];
numMasks += ((ldata & BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY) > 0) ? 1
: 0;
numOverviews += ((ldata & BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) > 0) ? 1
: 0;
numMaskOverView += ((ldata & BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) > 0 && (ldata & BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY) > 0) ? 1
: 0;
}
}
}
// Restore current Index
seekToImage(currentIdx);
// Setting Masks number and Overviews number
layout.setNumInternalMasks(numMasks);
layout.setNumInternalOverviews(numOverviews - numMaskOverView);
}
/**
* Method which checks if external masks or overviews are present and, if so,
* updates the {@link DatasetLayout}
*
* @throws IIOException
*/
private void defineExternalMasks() throws IIOException {
// Check if already initialized
if ((externalMask == null || layout.getExternalMasks() != null)
&& (maskOverviews == null || layout.getExternalMaskOverviews() != null)
&& (externalOverviews == null || layout.getExternalOverviews() != null)) {
return;
}
// Initialize
readHeader();
// Setting Values to the DatasetLayout
layout.setExternalMasks(externalMask);
layout.setExternalMaskOverviews(maskOverviews);
layout.setExternalOverviews(externalOverviews);
layout.setNumExternalMasks(getNumImages(externalMask));
layout.setNumExternalMaskOverviews(getNumImages(maskOverviews));
layout.setNumExternalOverviews(getNumImages(externalOverviews));
}
/**
* Simple method for accessing the number of images inside an input file
*
* @param inputFile Input {@link File} to check
* @return An integer defining the number of images contained in the input file
* @throws IIOException
*/
private int getNumImages(File inputFile) throws IIOException {
// Init value to 0
int numImg = 0;
// Creating a new TIFFImageReader instance for reading
ImageReader reader = null;
// Initializing a null stream
FileImageInputStreamExtImpl stream = null;
// Searching for external Overviews
if (inputFile != null) {
try {
// Getting mask data stream
stream = new FileImageInputStreamExtImpl(inputFile);
// Creating the Reader
reader = originatingProvider.createReaderInstance();
reader.setInput(stream);
// Getting the image number (Indicates the Mask number)
numImg = reader.getNumImages(true);
} catch (IOException e) {
throw new IIOException("Unable to open input .msk file", e);
} finally {
// Closing reader
if (reader != null) {
try {
reader.dispose();
} catch (Exception e) {
// Eat the Exception
}
}
// Closing stream
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
// Eat the Exception
}
}
}
}
return numImg;
}
private int locateImage(int imageIndex) throws IIOException {
readHeader();
try {
// Find closest known index
int index = Math.min(imageIndex, imageStartPosition.size() - 1);
// Seek to that position
long l = imageStartPosition.get(index);
stream.seek(l);
// Skip IFDs until at desired index or last image found
switch(magic) {
case 42:
while (index < imageIndex) {
int count = stream.readUnsignedShort();
stream.skipBytes(12 * count);
long offset = stream.readUnsignedInt();
if (offset == 0) {
if(DEBUG)
System.out.println("Offset 0 in locate");
currIndex=index;
imageMetadata = null;
// the current image index has changed, we got to reinitialized
initialized = false;
return index;
}
imageStartPosition.add(Long.valueOf(offset));
stream.seek(offset);
++index;
}
break;
case 43:
while (index < imageIndex) {
long count = stream.readLong();
stream.skipBytes(20*count);
long offset = stream.readLong();
if (offset == 0) {
currIndex=index;
imageMetadata = null;
// the current image index has changed, we got to reinitialized
initialized = false;
return index;
}
imageStartPosition.add(Long.valueOf(offset));
stream.seek(offset);
++index;
}
break;
}
} catch (IOException e) {
throw new IIOException("Couldn't seek!", e);
}
// are we changing ImageIndex??? If so, we got to reload the ImageMetadata as well as the
// various fields we initialized from them!
if (currIndex != imageIndex) {
imageMetadata = null;
// the current image index has changed, we got to reinitialized
initialized = false;
}
currIndex = imageIndex;
return imageIndex;
}
public int getNumImages(boolean allowSearch) throws IOException {
if (stream == null) {
throw new IllegalStateException("Input not set!");
}
if (seekForwardOnly && allowSearch) {
throw new IllegalStateException
("seekForwardOnly and allowSearch can't both be true!");
}
if (numImages > 0) {
return numImages;
}
if (allowSearch) {
this.numImages = locateImage(Integer.MAX_VALUE) + 1;
}
return numImages;
}
public IIOMetadata getStreamMetadata() throws IIOException {
readHeader();
// Defining DatasetLayout
defineDatasetLayout();
// Defining External Masks
defineExternalMasks();
// Setting the Layout
streamMetadata.dtLayout = layout;
return streamMetadata;
}
/**
* Throw an IndexOutOfBoundsException if index < minIndex, and bump minIndex if required.
* @param imageIndex
*/
private void checkIndex(int imageIndex) {
if (imageIndex < minIndex) {
throw new IndexOutOfBoundsException("imageIndex < minIndex!");
}
if (seekForwardOnly) {
minIndex = imageIndex;
}
}
/**
* Verify that imageIndex is in bounds, find the image IFD, read the image metadata, initialize instance variables from the metadata.
* @param imageIndex
* @throws IIOException
*/
private void seekToImage(int imageIndex) throws IIOException {
seekToImage(imageIndex, true);
}
/**
* Verify that imageIndex is in bounds, find the image IFD, read the image metadata, initialize instance variables from the metadata.
* @param imageIndex
* @param optimized
* @throws IIOException
*/
private void seekToImage(int imageIndex, boolean optimized) throws IIOException {
checkIndex(imageIndex);
// TODO we should do this initialization just once!!!
int index = locateImage(imageIndex);
if (index != imageIndex) {
throw new IndexOutOfBoundsException("imageIndex out of bounds!");
}
final Integer i= Integer.valueOf(index);
//optimized branch
if(!optimized){
readMetadata();
initializeFromMetadata();
return;
}
// in case we have cache the info for this page
if(pagesInfo.containsKey(i)){
// initialize from cachedinfo only if needed
// TODO Improve
if(imageMetadata == null || !initialized) {// this means the curindex has changed
final PageInfo info = pagesInfo.get(i);
final TIFFImageMetadata metadata = info.imageMetadata.get();
if (metadata != null) {
initializeFromCachedInfo(info, metadata);
return;
}
pagesInfo.put(i,null);
}
}
readMetadata();
initializeFromMetadata();
}
private void initializeFromCachedInfo(PageInfo pageInfo, TIFFImageMetadata imageMetadata) {
this.bigtiff = pageInfo.bigtiff;
this.bitsPerSample = pageInfo.bitsPerSample;
this.colorMap = pageInfo.colorMap;
this.compression = pageInfo.compression;
this.extraSamples = pageInfo.extraSamples;
this.height = pageInfo.height;
this.isImageTiled = pageInfo.isImageTiled;
this.numBands = pageInfo.numBands;
this.photometricInterpretation = pageInfo.photometricInterpretation;
this.planarConfiguration = pageInfo.planarConfiguration;
this.sampleFormat = pageInfo.sampleFormat;
this.samplesPerPixel = pageInfo.samplesPerPixel;
this.tileOrStripHeight = pageInfo.tileOrStripHeight;
this.tileOrStripWidth = pageInfo.tileOrStripWidth;
this.width = pageInfo.width;
this.noData = pageInfo.noData;
this.imageMetadata = imageMetadata;
}
// Stream must be positioned at start of IFD for 'currIndex'
private void readMetadata() throws IIOException {
if (imageMetadata != null) {
return;
}
if (stream == null) {
throw new IllegalStateException("Input not set!");
}
try {
// Create an object to store the image metadata
List<BaselineTIFFTagSet> tagSets;
if (imageReadParam instanceof TIFFImageReadParam) {
tagSets =
((TIFFImageReadParam)imageReadParam).getAllowedTagSets();
} else {
tagSets = new ArrayList<BaselineTIFFTagSet>(1);
tagSets.add(BaselineTIFFTagSet.getInstance());
}
this.imageMetadata = new TIFFImageMetadata(tagSets);
imageMetadata.initializeFromStream(stream, ignoreMetadata, bigtiff);
// we got to reinitialized!!!
initialized = false;
} catch (IIOException iioe) {
throw iioe;
} catch (IOException ioe) {
throw new IIOException("I/O error reading image metadata!", ioe);
}
}
// Returns tile width if image is tiled, else image width
private int getTileOrStripWidth() {
TIFFField f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
return (f == null) ? width : f.getAsInt(0);
}
// Returns tile height if image is tiled, else strip height
private int getTileOrStripHeight() {
int h =-1;
TIFFField f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
if (f != null) {
h= f.getAsInt(0);
}else{
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
// Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
h = (f == null) ? -1 : f.getAsInt(0);
}
return (h == -1) ? height : h;
}
private int getPlanarConfiguration() {
TIFFField f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
if (f != null) {
int planarConfigurationValue = f.getAsInt(0);
if(planarConfigurationValue ==
BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
// Some writers (e.g. Kofax standard Multi-Page TIFF
// Storage Filter v2.01.000; cf. bug 4929147) do not
// correctly set the value of this field. Attempt to
// ascertain whether the value is correctly Planar.
if(compression ==
BaselineTIFFTagSet.COMPRESSION_OLD_JPEG &&
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) !=
null) {
// JPEG interchange format cannot have
// PlanarConfiguration value Chunky so reset.
processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
planarConfigurationValue =
BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
} else {
TIFFField offsetField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
if (offsetField == null) {
// Tiles
offsetField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
int tw = tileOrStripWidth;
int th = tileOrStripHeight;
int tAcross = (width + tw - 1)/tw;
int tDown = (height + th - 1)/th;
int tilesPerImage = tAcross*tDown;
long[] offsetArray = offsetField.getAsLongs();
if(offsetArray != null &&
offsetArray.length == tilesPerImage) {
// Length of offsets array is
// TilesPerImage for Chunky and
// SamplesPerPixel*TilesPerImage for Planar.
processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
planarConfigurationValue =
BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
}
} else {
// Strips
int rowsPerStrip = tileOrStripHeight;
int stripsPerImage =
(height + rowsPerStrip - 1)/rowsPerStrip;
long[] offsetArray = offsetField.getAsLongs();
if(offsetArray != null &&
offsetArray.length == stripsPerImage) {
// Length of offsets array is
// StripsPerImage for Chunky and
// SamplesPerPixel*StripsPerImage for Planar.
processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
planarConfigurationValue =
BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
}
}
}
}
return planarConfigurationValue;
}
return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
}
private long getTileOrStripOffset(int tileIndex) throws IIOException {
TIFFField f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
if (f == null) {
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
}
if (f == null) {
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
}
if(f == null) {
throw new IIOException
("Missing required strip or tile offsets field.");
}
return f.getAsLong(tileIndex);
}
private long getTileOrStripByteCount(int tileIndex) throws IOException {
TIFFField f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
if (f == null) {
f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
}
if (f == null) {
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
}
long tileOrStripByteCount;
if(f != null) {
tileOrStripByteCount = f.getAsLong(tileIndex);
} else {
processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");
// Initialize to number of bytes per strip or tile assuming
// no compression.
int bitsPerPixel = bitsPerSample[0];
for(int i = 1; i < samplesPerPixel; i++) {
bitsPerPixel += bitsPerSample[i];
}
int bytesPerRow = (tileOrStripWidth*bitsPerPixel + 7)/8;
tileOrStripByteCount = bytesPerRow*tileOrStripHeight;
// Clamp to end of stream if possible.
long streamLength = stream.length();
if(streamLength != -1) {
tileOrStripByteCount =
Math.min(tileOrStripByteCount,
streamLength - getTileOrStripOffset(tileIndex));
} else {
processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
}
}
return tileOrStripByteCount;
}
private int getCompression() {
TIFFField f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
if (f == null) {
processWarningOccurred("Compression field is missing; assuming no compression");
return BaselineTIFFTagSet.COMPRESSION_NONE;
} else {
return f.getAsInt(0);
}
}
public int getWidth(int imageIndex) throws IOException {
seekToImage(imageIndex);
return width;
}
public int getHeight(int imageIndex) throws IOException {
seekToImage(imageIndex);
return height;
}
/**
* Initializes these instance variables from the image metadata:
* <pre>
* compression
* width
* height
* samplesPerPixel
* numBands
* colorMap
* photometricInterpretation
* sampleFormat
* bitsPerSample
* extraSamples
* tileOrStripWidth
* tileOrStripHeight
* </pre>
*/
private void initializeFromMetadata() {
if(initialized)
return;
//
// Planar Config
//
this.planarConfiguration = getPlanarConfiguration();
//
// Compression
//
compression=getCompression();
// Whether key dimensional information is absent.
boolean isMissingDimension = false;
//
// ImageWidth -> width
//
TIFFField f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
if (f != null) {
this.width = f.getAsInt(0);
} else {
processWarningOccurred("ImageWidth field is missing.");
isMissingDimension = true;
}
//
// ImageLength -> height
//
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
if (f != null) {
this.height = f.getAsInt(0);
} else {
processWarningOccurred("ImageLength field is missing.");
isMissingDimension = true;
}
//
// Tiling
//
tileOrStripWidth = getTileOrStripWidth();
tileOrStripHeight = getTileOrStripHeight();
f =imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
isImageTiled = f != null;
//
// SamplesPerPixel
//
f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
if (f != null) {
samplesPerPixel = f.getAsInt(0);
} else {
samplesPerPixel = 1;
isMissingDimension = true;
}
// If any dimension is missing and there is a JPEG stream available
// get the information from it.
int defaultBitDepth = 1;
if(isMissingDimension &&
(f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG");
if(iter != null && iter.hasNext()) {
ImageReader jreader = iter.next();
try {
stream.mark();
stream.seek(f.getAsLong(0));
jreader.setInput(stream);
if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
this.width = jreader.getWidth(0);
}
if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
this.height = jreader.getHeight(0);
}
ImageTypeSpecifier imageType = jreader.getRawImageType(0);
if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
this.samplesPerPixel =
imageType.getSampleModel().getNumBands();
}
stream.reset();
defaultBitDepth =
imageType.getColorModel().getComponentSize(0);
} catch(IOException e) {
// Ignore it and proceed: an error will occur later.
}
jreader.dispose();
}
}
if (samplesPerPixel < 1) {
processWarningOccurred("Samples per pixel < 1!");
}
//
// SamplesPerPixel -> numBands
//
numBands = samplesPerPixel;
//
// ColorMap
//
this.colorMap = null;
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
if (f != null) {
// Grab color map
colorMap = f.getAsChars();
}
//
// PhotometricInterpretation
//
f =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
if (f == null) {
if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE ||
compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4 ||
compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
processWarningOccurred
("PhotometricInterpretation field is missing; "+
"assuming WhiteIsZero");
photometricInterpretation =
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
} else if(this.colorMap != null) {
photometricInterpretation =
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
} else if(samplesPerPixel == 3 || samplesPerPixel == 4) {
photometricInterpretation =
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
} else {
processWarningOccurred
("PhotometricInterpretation field is missing; "+
"assuming BlackIsZero");
photometricInterpretation =
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
}
} else {
photometricInterpretation = f.getAsInt(0);
}
//
// SampleFormat
//
boolean replicateFirst = false;
int first = -1;
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
sampleFormat = new int[samplesPerPixel];
replicateFirst = false;
if (f == null) {
replicateFirst = true;
first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
} else if (f.getCount() != samplesPerPixel) {
replicateFirst = true;
first = f.getAsInt(0);
}
for (int i = 0; i < samplesPerPixel; i++) {
sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
if (sampleFormat[i] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER &&
sampleFormat[i] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER &&
sampleFormat[i] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT &&
sampleFormat[i] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
processWarningOccurred(
"Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
}
}
//
// BitsPerSample
//
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
this.bitsPerSample = new int[samplesPerPixel];
replicateFirst = false;
if (f == null) {
replicateFirst = true;
first = defaultBitDepth;
} else if (f.getCount() != samplesPerPixel) {
replicateFirst = true;
first = f.getAsInt(0);
}
for (int i = 0; i < samplesPerPixel; i++) {
// Replicate initial value if not enough values provided
bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
if (DEBUG) {
System.out.println("bitsPerSample[" + i + "] = "
+ bitsPerSample[i]);
}
}
//
// ExtraSamples
//
this.extraSamples = null;
f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
if (f != null) {
extraSamples = f.getAsInts();
}
//
// NoData (if any, leveraging on GDAL tag)
//
this.noData = null;
f = imageMetadata.getTIFFField(PrivateTIFFTagSet.TAG_GDAL_NODATA);
if (f != null) {
String value = f.getAsString(0);
if ("nan".equalsIgnoreCase(value)) {
noData = Double.NaN;
} else {
noData = Double.parseDouble(value);
}
}
// signal that this image is initialized
initialized = true;
// cache the page info for later reuse
pagesInfo.put(
currIndex,
new PageInfo(
imageMetadata,
bigtiff,
bitsPerSample,
colorMap,
compression,
height,
numBands,
photometricInterpretation,
width,
tileOrStripWidth,
tileOrStripHeight,
planarConfiguration,
isImageTiled,
samplesPerPixel,
sampleFormat,
extraSamples,
noData));
}
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
Integer imageIndexInteger = Integer.valueOf(imageIndex);
if(imageTypeMap.containsKey(imageIndexInteger))
// Return the cached ITS List.
return imageTypeMap.get(imageIndexInteger).iterator();
// Create a new ITS List.
final List<ImageTypeSpecifier> l= new ArrayList<ImageTypeSpecifier>();
// Create the ITS and cache if for later use so that this method
// always returns an Iterator containing the same ITS objects.
seekToImage(imageIndex, true);
ImageTypeSpecifier itsRaw =
TIFFDecompressor.getRawImageTypeSpecifier
(photometricInterpretation,
compression,
samplesPerPixel,
bitsPerSample,
sampleFormat,
extraSamples,
colorMap);
// Check for an ICCProfile field.
TIFFField iccProfileField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);
// If an ICCProfile field is present change the ImageTypeSpecifier
// to use it if the data layout is component type.
if(iccProfileField != null &&
itsRaw.getColorModel() instanceof ComponentColorModel) {
// Create a ColorSpace from the profile.
byte[] iccProfileValue = iccProfileField.getAsBytes();
ICC_Profile iccProfile =
ICC_Profile.getInstance(iccProfileValue);
ICC_ColorSpace iccColorSpace =
new ICC_ColorSpace(iccProfile);
// Get the raw sample and color information.
ColorModel cmRaw = itsRaw.getColorModel();
ColorSpace csRaw = cmRaw.getColorSpace();
SampleModel smRaw = itsRaw.getSampleModel();
// Get the number of samples per pixel and the number
// of color components.
int numBands = smRaw.getNumBands();
int numComponents = iccColorSpace.getNumComponents();
// Replace the ColorModel with the ICC ColorModel if the
// numbers of samples and color components are amenable.
if(numBands == numComponents ||
numBands == numComponents + 1) {
// Set alpha flags.
boolean hasAlpha = numComponents != numBands;
boolean isAlphaPre =
hasAlpha && cmRaw.isAlphaPremultiplied();
// Create a ColorModel of the same class and with
// the same transfer type.
ColorModel iccColorModel =
new ComponentColorModel(iccColorSpace,
cmRaw.getComponentSize(),
hasAlpha,
isAlphaPre,
cmRaw.getTransparency(),
cmRaw.getTransferType());
// Prepend the ICC profile-based ITS to the List. The
// ColorModel and SampleModel are guaranteed to be
// compatible as the old and new ColorModels are both
// ComponentColorModels with the same transfer type
// and the same number of components.
l.add(new ImageTypeSpecifier(iccColorModel, smRaw));
// Append the raw ITS to the List if and only if its
// ColorSpace has the same type and number of components
// as the ICC ColorSpace.
if(csRaw.getType() == iccColorSpace.getType() &&
csRaw.getNumComponents() ==
iccColorSpace.getNumComponents()) {
l.add(itsRaw);
}
} else { // ICCProfile not compatible with SampleModel.
// Append the raw ITS to the List.
l.add(itsRaw);
}
} else { // No ICCProfile field or raw ColorModel not component.
// Append the raw ITS to the List.
l.add(itsRaw);
}
// Cache the ITS List.
imageTypeMap.put(imageIndexInteger, l);
return l.iterator();
}
public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
seekToImage(imageIndex, true);
TIFFImageMetadata im =
new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());
Node root =
imageMetadata.getAsTree(TIFFImageMetadata.nativeMetadataFormatName);
im.setFromTree(TIFFImageMetadata.nativeMetadataFormatName, root);
return im;
}
public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException {
readHeader();
TIFFStreamMetadata sm = new TIFFStreamMetadata();
Node root = sm.getAsTree(TIFFStreamMetadata.nativeMetadataFormatName);
sm.setFromTree(TIFFStreamMetadata.nativeMetadataFormatName, root);
return sm;
}
public boolean isRandomAccessEasy(int imageIndex) throws IOException {
if(currIndex != -1) {
seekToImage(currIndex);
return compression == BaselineTIFFTagSet.COMPRESSION_NONE;
} else {
return false;
}
}
// Thumbnails
public boolean readSupportsThumbnails() {
return false;
}
public boolean hasThumbnails(int imageIndex) {
return false;
}
public int getNumThumbnails(int imageIndex) throws IOException {
return 0;
}
public ImageReadParam getDefaultReadParam() {
return new TIFFImageReadParam();
}
public boolean isImageTiled(int imageIndex) throws IOException {
seekToImage(imageIndex);
return isImageTiled;
}
public int getTileWidth(int imageIndex) throws IOException {
seekToImage(imageIndex);
return tileOrStripWidth;
}
public int getTileHeight(int imageIndex) throws IOException {
seekToImage(imageIndex);
return tileOrStripHeight;
}
public BufferedImage readTile(int imageIndex, int tileX, int tileY)
throws IOException {
int w = getWidth(imageIndex);
int h = getHeight(imageIndex);
int tw = getTileWidth(imageIndex);
int th = getTileHeight(imageIndex);
int x = tw*tileX;
int y = th*tileY;
if(tileX < 0 || tileY < 0 || x >= w || y >= h) {
throw new IllegalArgumentException
("Tile indices are out of bounds!");
}
if (x + tw > w) {
tw = w - x;
}
if (y + th > h) {
th = h - y;
}
ImageReadParam param = getDefaultReadParam();
Rectangle tileRect = new Rectangle(x, y, tw, th);
param.setSourceRegion(tileRect);
return read(imageIndex, param);
}
public boolean canReadRaster() {
// Enable this?
return false;
}
public Raster readRaster(int imageIndex, ImageReadParam param)
throws IOException {
// Enable this?
throw new UnsupportedOperationException();
}
private int[] sourceBands;
private int[] destinationBands;
private TIFFDecompressor decompressor;
// floor(num/den)
private static int ifloor(int num, int den) {
if (num < 0) {
num -= den - 1;
}
return num/den;
}
// ceil(num/den)
private static int iceil(int num, int den) {
if (num > 0) {
num += den - 1;
}
return num/den;
}
private void prepareRead(int imageIndex, ImageReadParam param)
throws IOException {
if (stream == null) {
throw new IllegalStateException("Input not set!");
}
// A null ImageReadParam means we use the default
if (param == null) {
param = getDefaultReadParam();
}
this.imageReadParam = param;
// ensure everything is initialized
seekToImage(imageIndex);
this.sourceBands = param.getSourceBands();
if (sourceBands == null) {
sourceBands = new int[numBands];
for (int i = 0; i < numBands; i++) {
sourceBands[i] = i;
}
}
// Initialize the destination image
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
ImageTypeSpecifier theImageType =
ImageUtil.getDestinationType(param, imageTypes);
int destNumBands = theImageType.getSampleModel().getNumBands();
this.destinationBands = param.getDestinationBands();
if (destinationBands == null) {
destinationBands = new int[destNumBands];
for (int i = 0; i < destNumBands; i++) {
destinationBands[i] = i;
}
}
if (sourceBands.length != destinationBands.length) {
throw new IllegalArgumentException(
"sourceBands.length != destinationBands.length");
}
for (int i = 0; i < sourceBands.length; i++) {
int sb = sourceBands[i];
if (sb < 0 || sb >= numBands) {
throw new IllegalArgumentException(
"Source band out of range!");
}
int db = destinationBands[i];
if (db < 0 || db >= destNumBands) {
throw new IllegalArgumentException(
"Destination band out of range!");
}
}
}
public RenderedImage readAsRenderedImage(int imageIndex,
ImageReadParam param)
throws IOException {
prepareRead(imageIndex, param);
return new TIFFRenderedImage(this, imageIndex, imageReadParam,
width, height);
}
private void decodeTile(int ti, int tj, int band) throws IOException {
if(DEBUG) {
System.out.println("decodeTile("+ti+","+tj+","+band+")");
}
// Compute the region covered by the strip or tile
Rectangle tileRect = new Rectangle(ti*tileOrStripWidth,
tj*tileOrStripHeight,
tileOrStripWidth,
tileOrStripHeight);
// Clip against the image bounds if the image is not tiled. If it
// is tiled, the tile may legally extend beyond the image bounds.
if(!this.isImageTiled) {
tileRect =
tileRect.intersection(new Rectangle(0, 0, width, height));
}
// Return if the intersection is empty.
if(tileRect.width <= 0 || tileRect.height <= 0) {
return;
}
int srcMinX = tileRect.x;
int srcMinY = tileRect.y;
int srcWidth = tileRect.width;
int srcHeight = tileRect.height;
// Determine dest region that can be derived from the
// source region
dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
srcXSubsampling);
dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
srcYSubsampling);
dstWidth = dstMaxX - dstMinX + 1;
dstHeight = dstMaxY - dstMinY + 1;
dstMinX += dstXOffset;
dstMinY += dstYOffset;
// Clip against image bounds
Rectangle dstRect = new Rectangle(dstMinX, dstMinY,
dstWidth, dstHeight);
dstRect =
dstRect.intersection(theImage.getRaster().getBounds());
dstMinX = dstRect.x;
dstMinY = dstRect.y;
dstWidth = dstRect.width;
dstHeight = dstRect.height;
if (dstWidth <= 0 || dstHeight <= 0) {
return;
}
// Backwards map dest region to source to determine
// active source region
int activeSrcMinX = (dstMinX - dstXOffset)*srcXSubsampling +
sourceXOffset;
int sxmax =
(dstMinX + dstWidth - 1 - dstXOffset)*srcXSubsampling +
sourceXOffset;
int activeSrcWidth = sxmax - activeSrcMinX + 1;
int activeSrcMinY = (dstMinY - dstYOffset)*srcYSubsampling +
sourceYOffset;
int symax =
(dstMinY + dstHeight - 1 - dstYOffset)*srcYSubsampling +
sourceYOffset;
int activeSrcHeight = symax - activeSrcMinY + 1;
decompressor.setSrcMinX(srcMinX);
decompressor.setSrcMinY(srcMinY);
decompressor.setSrcWidth(srcWidth);
decompressor.setSrcHeight(srcHeight);
decompressor.setDstMinX(dstMinX);
decompressor.setDstMinY(dstMinY);
decompressor.setDstWidth(dstWidth);
decompressor.setDstHeight(dstHeight);
decompressor.setActiveSrcMinX(activeSrcMinX);
decompressor.setActiveSrcMinY(activeSrcMinY);
decompressor.setActiveSrcWidth(activeSrcWidth);
decompressor.setActiveSrcHeight(activeSrcHeight);
int tileIndex = tj*tilesAcross + ti;
if (planarConfiguration ==
BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
tileIndex += band*tilesAcross*tilesDown;
}
long offset = getTileOrStripOffset(tileIndex);
long byteCount = getTileOrStripByteCount(tileIndex);
//
// Attempt to handle truncated streams, i.e., where reading the
// compressed strip or tile would result in an EOFException. The
// number of bytes to read is clamped to the number available
// from the stream starting at the indicated position in the hope
// that the decompressor will handle it.
//
long streamLength = stream.length();
if(streamLength > 0 && offset + byteCount > streamLength) {
processWarningOccurred("Attempting to process truncated stream.");
if(Math.max(byteCount = streamLength - offset, 0) == 0) {
processWarningOccurred("No bytes in strip/tile: skipping.");
return;
}
}
decompressor.setStream(stream);
decompressor.setOffset(offset);
decompressor.setByteCount((int)byteCount);
((TIFFDecompressor)decompressor).setNoData(noData);
decompressor.beginDecoding();
stream.mark();
decompressor.decode();
stream.reset();
}
private void reportProgress() {
// Report image progress/update to listeners after each tile
pixelsRead += dstWidth*dstHeight;
processImageProgress(100.0f*pixelsRead/pixelsToRead);
processImageUpdate(theImage,
dstMinX, dstMinY, dstWidth, dstHeight,
1, 1,
destinationBands);
}
public BufferedImage read(int imageIndex, ImageReadParam param)
throws IOException {
prepareRead(imageIndex, param);
this.theImage = getDestination(param,
getImageTypes(imageIndex),
width, height);
srcXSubsampling = imageReadParam.getSourceXSubsampling();
srcYSubsampling = imageReadParam.getSourceYSubsampling();
Point p = imageReadParam.getDestinationOffset();
dstXOffset = p.x;
dstYOffset = p.y;
// This could probably be made more efficient...
Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
Rectangle destRegion = new Rectangle(0, 0, 0, 0);
computeRegions(imageReadParam, width, height, theImage,
srcRegion, destRegion);
// Initial source pixel, taking source region and source
// subsamplimg offsets into account
sourceXOffset = srcRegion.x;
sourceYOffset = srcRegion.y;
pixelsToRead = destRegion.width*destRegion.height;
pixelsRead = 0;
processImageStarted(imageIndex);
processImageProgress(0.0f);
tilesAcross = (width + tileOrStripWidth - 1)/tileOrStripWidth;
tilesDown = (height + tileOrStripHeight - 1)/tileOrStripHeight;
// Attempt to get decompressor and color converted from the read param
TIFFColorConverter colorConverter = null;
if (imageReadParam instanceof TIFFImageReadParam) {
TIFFImageReadParam tparam =
(TIFFImageReadParam)imageReadParam;
this.decompressor = tparam.getTIFFDecompressor();
colorConverter = tparam.getColorConverter();
}
// If we didn't find one, use a standard decompressor
if (this.decompressor == null) {
if (compression ==
BaselineTIFFTagSet.COMPRESSION_NONE) {
// Get the fillOrder field.
TIFFField fillOrderField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
// Set the decompressor based on the fill order.
if(fillOrderField != null && fillOrderField.getAsInt(0) == 2) {
this.decompressor = new TIFFLSBDecompressor();
} else {
this.decompressor = new TIFFNullDecompressor();
}
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
// Try to create the codecLib decompressor.
if(PackageUtil.isCodecLibAvailable()) {
try {
this.decompressor =
new TIFFCodecLibFaxDecompressor(compression);
if(DEBUG) {
System.out.println
("Using codecLib T.6 decompressor");
}
} catch (RuntimeException re) {
if(DEBUG) {
System.out.println(re);
}
}
}
// Fall back to the Java decompressor.
if (this.decompressor == null) {
if(DEBUG) {
System.out.println("Using Java T.6 decompressor");
}
this.decompressor = new TIFFFaxDecompressor();
}
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
if(PackageUtil.isCodecLibAvailable()) {
// Try to create the codecLib decompressor.
try {
this.decompressor =
new TIFFCodecLibFaxDecompressor(compression);
if(DEBUG) {
System.out.println
("Using codecLib T.4 decompressor");
}
} catch (RuntimeException re) {
if(DEBUG) {
System.out.println(re);
}
}
}
// Fall back to the Java decompressor.
if (this.decompressor == null) {
if(DEBUG) {
System.out.println("Using Java T.4 decompressor");
}
this.decompressor = new TIFFFaxDecompressor();
}
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
this.decompressor = new TIFFFaxDecompressor();
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
if(DEBUG) {
System.out.println("Using TIFFPackBitsDecompressor");
}
this.decompressor = new TIFFPackBitsDecompressor();
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_LZW) {
if(DEBUG) {
System.out.println("Using TIFFLZWDecompressor");
}
TIFFField predictorField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
int predictor = ((predictorField == null) ?
BaselineTIFFTagSet.PREDICTOR_NONE :
predictorField.getAsInt(0));
this.decompressor = new TIFFLZWDecompressor(predictor);
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_JPEG) {
this.decompressor = new TIFFJPEGDecompressor();
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_ZLIB ||
compression ==
BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
TIFFField predictorField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
int predictor = ((predictorField == null) ?
BaselineTIFFTagSet.PREDICTOR_NONE :
predictorField.getAsInt(0));
this.decompressor = new TIFFDeflateDecompressor(predictor);
} else if (compression ==
BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
TIFFField JPEGProcField =
imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
if(JPEGProcField == null) {
processWarningOccurred
("JPEGProc field missing; assuming baseline sequential JPEG process.");
} else if(JPEGProcField.getAsInt(0) !=
BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
throw new IIOException
("Old-style JPEG supported for baseline sequential JPEG process only!");
}
this.decompressor = new TIFFOldJPEGDecompressor();
//throw new IIOException("Old-style JPEG not supported!");
} else {
throw new IIOException
("Unsupported compression type (tag number = "+
compression+")!");
}
if (photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
compression != BaselineTIFFTagSet.COMPRESSION_JPEG &&
compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
boolean convertYCbCrToRGB =
theImage.getColorModel().getColorSpace().getType() ==
ColorSpace.TYPE_RGB;
TIFFDecompressor wrappedDecompressor =
this.decompressor instanceof TIFFNullDecompressor ?
null : this.decompressor;
this.decompressor =
new TIFFYCbCrDecompressor(wrappedDecompressor,
convertYCbCrToRGB);
}
}
if(DEBUG) {
System.out.println("\nDecompressor class = "+
decompressor.getClass().getName()+"\n");
}
if (colorConverter == null) {
if (photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB &&
theImage.getColorModel().getColorSpace().getType() ==
ColorSpace.TYPE_RGB) {
colorConverter = new TIFFCIELabColorConverter();
} else if (photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
!(this.decompressor instanceof TIFFYCbCrDecompressor) &&
compression != BaselineTIFFTagSet.COMPRESSION_JPEG &&
compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
}
}
decompressor.setReader(this);
decompressor.setMetadata(imageMetadata);
decompressor.setImage(theImage);
decompressor.setPhotometricInterpretation(photometricInterpretation);
decompressor.setCompression(compression);
decompressor.setSamplesPerPixel(samplesPerPixel);
decompressor.setBitsPerSample(bitsPerSample);
decompressor.setSampleFormat(sampleFormat);
decompressor.setExtraSamples(extraSamples);
decompressor.setColorMap(colorMap);
decompressor.setColorConverter(colorConverter);
decompressor.setSourceXOffset(sourceXOffset);
decompressor.setSourceYOffset(sourceYOffset);
decompressor.setSubsampleX(srcXSubsampling);
decompressor.setSubsampleY(srcYSubsampling);
decompressor.setDstXOffset(dstXOffset);
decompressor.setDstYOffset(dstYOffset);
decompressor.setSourceBands(sourceBands);
decompressor.setDestinationBands(destinationBands);
// Compute bounds on the tile indices for this source region.
int minTileX =
TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth);
int minTileY =
TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight);
int maxTileX =
TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1,
0, tileOrStripWidth);
int maxTileY =
TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1,
0, tileOrStripHeight);
boolean isAbortRequested = false;
if (planarConfiguration ==
BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
decompressor.setPlanar(true);
int[] sb = new int[1];
int[] db = new int[1];
for (int tj = minTileY; tj <= maxTileY; tj++) {
for (int ti = minTileX; ti <= maxTileX; ti++) {
for (int band = 0; band < numBands; band++) {
sb[0] = sourceBands[band];
decompressor.setSourceBands(sb);
db[0] = destinationBands[band];
decompressor.setDestinationBands(db);
//XXX decompressor.beginDecoding();
// The method abortRequested() is synchronized
// so check it only once per loop just before
// doing any actual decoding.
if(abortRequested()) {
isAbortRequested = true;
break;
}
decodeTile(ti, tj, band);
}
if(isAbortRequested) break;
reportProgress();
}
if(isAbortRequested) break;
}
} else {
//XXX decompressor.beginDecoding();
for (int tj = minTileY; tj <= maxTileY; tj++) {
for (int ti = minTileX; ti <= maxTileX; ti++) {
// The method abortRequested() is synchronized
// so check it only once per loop just before
// doing any actual decoding.
if(abortRequested()) {
isAbortRequested = true;
break;
}
decodeTile(ti, tj, -1);
reportProgress();
}
if(isAbortRequested) break;
}
}
if (isAbortRequested) {
processReadAborted();
} else {
processImageComplete();
}
return theImage;
}
public void reset() {
super.reset();
resetLocal();
}
protected void resetLocal() {
imageStartPosition.clear();
pagesInfo.clear();
stream = null;
gotTiffHeader = false;
imageReadParam = getDefaultReadParam();
streamMetadata = null;
currIndex = -1;
imageMetadata = null;
initialized = false;
imageStartPosition = new ArrayList<Long>();
numImages = -1;
imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>();
width = -1;
height = -1;
numBands = -1;
tileOrStripWidth = -1;
tileOrStripHeight = -1;
planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
}
/**
* Package scope method to allow decompressors, for example, to
* emit warning messages.
*/
void forwardWarningMessage(String warning) {
processWarningOccurred(warning);
}
protected static BufferedImage getDestination(ImageReadParam param,
Iterator<ImageTypeSpecifier> imageTypes,
int width, int height)
throws IIOException {
if (imageTypes == null || !imageTypes.hasNext()) {
throw new IllegalArgumentException("imageTypes null or empty!");
}
BufferedImage dest = null;
ImageTypeSpecifier imageType = null;
// If param is non-null, use it
if (param != null) {
// Try to get the image itself
dest = param.getDestination();
if (dest != null) {
return dest;
}
// No image, get the image type
imageType = param.getDestinationType();
}
// No info from param, use fallback image type
if (imageType == null) {
Object o = imageTypes.next();
if (!(o instanceof ImageTypeSpecifier)) {
throw new IllegalArgumentException
("Non-ImageTypeSpecifier retrieved from imageTypes!");
}
imageType = (ImageTypeSpecifier)o;
} else {
boolean foundIt = false;
while (imageTypes.hasNext()) {
ImageTypeSpecifier type =
imageTypes.next();
if (type.equals(imageType)) {
foundIt = true;
break;
}
}
if (!foundIt) {
throw new IIOException
("Destination type from ImageReadParam does not match!");
}
}
Rectangle srcRegion = new Rectangle(0,0,0,0);
Rectangle destRegion = new Rectangle(0,0,0,0);
computeRegions(param,
width,
height,
null,
srcRegion,
destRegion);
int destWidth = destRegion.x + destRegion.width;
int destHeight = destRegion.y + destRegion.height;
// Create a new image based on the type specifier
if ((long)destWidth*destHeight > Integer.MAX_VALUE) {
throw new IllegalArgumentException
("width*height > Integer.MAX_VALUE!");
}
return imageType.createBufferedImage(destWidth, destHeight);
}
@Override
public void dispose() {
if (this.decompressor != null) {
this.decompressor.dispose();
}
this.layout = null;
if (this.theImage != null) {
this.theImage.flush();
}
this.theImage = null;
this.imageStartPosition = null;
this.imageMetadata = null;
this.imageReadParam = null;
this.stream = null;
}
}